# -*- coding: utf-8 -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

__all__ = ('IAddress', 'ITransport', 'ITCPTransport', 'ISSLTransport', 'IProtocol', 'IProtocolFactory',
	'IAccessLogFormatter', 'IProducer', 'IPushProducer', 'IConsumer', 'IListeningPort', 'IConnector',
	'IReactor', 'IReactorCore', 'IReactorTCP', 'IReactorTime')

import typing

import eof.txbr as txbr
import eof.ztk  as ztk

import eof.failure as failure


if typing.TYPE_CHECKING:
	from .protocol import ServerFactory
	from .defer import Deferred


class IAddress(ztk.Interface):
    """
    An address, e.g. a TCP C{(host, port)}.

    Default implementations are in L{twisted.internet.address}.
    """


class ITransport(ztk.Interface):
    """
    I am a transport for bytes.

    I represent (and wrap) the physical connection and synchronicity
    of the framework which is talking to the network.  I make no
    representations about whether calls to me will happen immediately
    or require returning to a control loop, or whether they will happen
    in the same or another thread.  Consider methods of this class
    (aside from getPeer) to be 'thrown over the wall', to happen at some
    indeterminate time.
    """

    def write(data: bytes) -> None:
        """
        Write some data to the physical connection, in sequence, in a
        non-blocking fashion.

        If possible, make sure that it is all written.  No data will
        ever be lost, although (obviously) the connection may be closed
        before it all gets through.

        @param data: The data to write.
        """

    def writeSequence(data: typing.Iterable[bytes]) -> None:
        """
        Write an iterable of byte strings to the physical connection.

        If possible, make sure that all of the data is written to
        the socket at once, without first copying it all into a
        single byte string.

        @param data: The data to write.
        """

    def loseConnection() -> None:
        """
        Close my connection, after writing all pending data.

        Note that if there is a registered producer on a transport it
        will not be closed until the producer has been unregistered.
        """

    def getPeer() -> IAddress:
        """
        Get the remote address of this connection.

        Treat this method with caution.  It is the unfortunate result of the
        CGI and Jabber standards, but should not be considered reliable for
        the usual host of reasons; port forwarding, proxying, firewalls, IP
        masquerading, etc.

        @return: An L{IAddress} provider.
        """

    def getHost() -> IAddress:
        """
        Similar to getPeer, but returns an address describing this side of the
        connection.

        @return: An L{IAddress} provider.
        """


class ITCPTransport(ITransport):
    """A TCP based transport."""

    def loseWriteConnection() -> None:
        """
        Half-close the write side of a TCP connection.

        If the protocol instance this is attached to provides
        IHalfCloseableProtocol, it will get notified when the operation is
        done. When closing write connection, as with loseConnection this will
        only happen when buffer has emptied and there is no registered
        producer.
        """

    def abortConnection() -> None:
        """
        Close the connection abruptly.

        Discards any buffered data, stops any registered producer,
        and, if possible, notifies the other end of the unclean
        closure.

        @since: 11.1
        """

    def getTcpNoDelay() -> bool:
        """
        Return if C{TCP_NODELAY} is enabled.
        """

    def setTcpNoDelay(enabled: bool) -> None:
        """
        Enable/disable C{TCP_NODELAY}.

        Enabling C{TCP_NODELAY} turns off Nagle's algorithm. Small packets are
        sent sooner, possibly at the expense of overall throughput.
        """

    def getTcpKeepAlive() -> bool:
        """
        Return if C{SO_KEEPALIVE} is enabled.
        """

    def setTcpKeepAlive(enabled: bool) -> None:
        """
        Enable/disable C{SO_KEEPALIVE}.

        Enabling C{SO_KEEPALIVE} sends packets periodically when the connection
        is otherwise idle, usually once every two hours. They are intended
        to allow detection of lost peers in a non-infinite amount of time.
        """

    def getHost() -> IAddress:
        """Returns L{IPv4Address} or L{IPv6Address}."""

    def getPeer() -> IAddress:
        """Returns L{IPv4Address} or L{IPv6Address}."""


class ISSLTransport(ITCPTransport):
    """A SSL/TLS based transport."""

    def getPeerCertificate():
        """Return an object with the peer's certificate info."""


class IProtocol(ztk.Interface):
    def dataReceived(data: bytes) -> None:
        """Called whenever data is received.

        Use this method to translate to a higher-level message.  Usually, some
        callback will be made upon the receipt of each complete protocol
        message.

        Please keep in mind that you will probably need to buffer some data
        as partial (or multiple) protocol messages may be received!  We
        recommend that unit tests for protocols call through to this method
        with differing chunk sizes, down to one byte at a time.

        @param data: bytes of indeterminate length"""

    def connectionLost(reason: failure.Failure) -> None:
        """Called when the connection is shut down.

        Clear any circular references here, and any external references
        to this Protocol.  The connection has been closed. The C{reason}
        Failure wraps a L{twisted.internet.error.ConnectionDone} or
        L{twisted.internet.error.ConnectionLost} instance (or a subclass
        of one of those)."""

    def makeConnection(transport: ITransport) -> None:
        """Make a connection to a transport and a server."""

    def connectionMade() -> None:
        """Called when a connection is made.

        This may be considered the initializer of the protocol, because
        it is called when the connection is completed.  For clients,
        this is called once the connection to the server has been
        established; for servers, this is called after an accept() call
        stops blocking and a socket has been received.  If you need to
        send any greeting or initial message, do it here."""


class IProtocolFactory(ztk.Interface):
	"""Interface for protocol factories."""

	def buildProtocol(addr: IAddress) -> typing.Optional[IProtocol]:
		"""Called when a connection has been established to addr.

		If None is returned, the connection is assumed to have been refused,
		and the Port will close the connection.

		@param addr: The address of the newly-established connection

		@return: None if the connection was refused, otherwise an object
				 providing L{IProtocol}."""

	def doStart() -> None:
		"""Called every time this is connected to a Port or Connector."""

	def doStop() -> None:
		"""Called every time this is unconnected from a Port or Connector."""


class IAccessLogFormatter(ztk.Interface):
    """
    An object which can represent an HTTP request as a line of text for
    inclusion in an access log file.
    """
    def __call__(timestamp, request):
        """
        Generate a line for the access log.

        @param timestamp: The time at which the request was completed in the
            standard format for access logs.
        @type timestamp: L{unicode}

        @param request: The request object about which to log.
        @type request: L{twisted.web.server.Request}

        @return: One line describing the request without a trailing newline.
        @rtype: L{unicode}
        """


class IProducer(ztk.Interface):
    """
    A producer produces data for a consumer.

    Typically producing is done by calling the C{write} method of a class
    implementing L{IConsumer}.
    """

    def stopProducing() -> None:
        """
        Stop producing data.

        This tells a producer that its consumer has died, so it must stop
        producing data for good.
        """


class IPushProducer(IProducer):
    """
    A push producer, also known as a streaming producer is expected to
    produce (write to this consumer) data on a continuous basis, unless
    it has been paused. A paused push producer will resume producing
    after its C{resumeProducing()} method is called.   For a push producer
    which is not pauseable, these functions may be noops.
    """

    def pauseProducing() -> None:
        """
        Pause producing data.

        Tells a producer that it has produced too much data to process for
        the time being, and to stop until C{resumeProducing()} is called.
        """

    def resumeProducing() -> None:
        """
        Resume producing data.

        This tells a producer to re-add itself to the main loop and produce
        more data for its consumer.
        """


class IConsumer(ztk.Interface):
    """A consumer consumes data from a producer."""

    def registerProducer(producer: IProducer, streaming: bool) -> None:
        """
        Register to receive data from a producer.

        This sets self to be a consumer for a producer.  When this object runs
        out of data (as when a send(2) call on a socket succeeds in moving the
        last data from a userspace buffer into a kernelspace buffer), it will
        ask the producer to resumeProducing().

        For L{IPullProducer} providers, C{resumeProducing} will be called once
        each time data is required.

        For L{IPushProducer} providers, C{pauseProducing} will be called
        whenever the write buffer fills up and C{resumeProducing} will only be
        called when it empties.  The consumer will only call C{resumeProducing}
        to balance a previous C{pauseProducing} call; the producer is assumed
        to start in an un-paused state.

        @param streaming: C{True} if C{producer} provides L{IPushProducer},
            C{False} if C{producer} provides L{IPullProducer}.

        @raise RuntimeError: If a producer is already registered.
        """

    def unregisterProducer() -> None:
        """
        Stop consuming data from a producer, without disconnecting.
        """

    def write(data: bytes) -> None:
        """
        The producer will write data by calling this method.

        The implementation must be non-blocking and perform whatever
        buffering is necessary.  If the producer has provided enough data
        for now and it is a L{IPushProducer}, the consumer may call its
        C{pauseProducing} method.
        """


class IListeningPort(ztk.Interface):
	"""A listening port."""

	def startListening() -> None:
		"""Start listening on this port.

	@raise CannotListenError: If it cannot listen on this port (e.g., it is
							  a TCP port and it cannot bind to the required
							  port number)."""

	def stopListening() -> None:
		"""	Stop listening on this port.

	If it does not complete immediately, will return Deferred that fires upon completion."""

	def getHost() -> IAddress:
		"""	Get the host that this port is listening for.

	@return: An L{IAddress} provider."""


class IConnector(ztk.Interface):
	"""Object used to interface between connections and protocols.

	Each L{IConnector} manages one connection."""

	def stopConnecting() -> None:
		"""Stop attempting to connect."""

	def disconnect() -> None:
		"""Disconnect regardless of the connection state.

	If we are connected, disconnect, if we are trying to connect, stop trying."""

	def connect() -> None:
		"""Try to connect to remote address."""

	def getDestination() -> IAddress:
		"""Return destination this will try to connect to.

	@return: An object which provides L{IAddress}."""


class IReactor(ztk.Interface):
	pass


class IReactorCore(ztk.Interface):
    """
    Core methods that a Reactor must implement.
    """

    running = ztk.Attribute(
        'A C{bool} which is C{True} from I{during startup} to '
        'I{during shutdown} and C{False} the rest of the time.'
    )

    def resolve(name: str, timeout: typing.Sequence[int]) -> 'Deferred':
        """
        Return a L{twisted.internet.defer.Deferred} that will resolve
        a hostname.
        """

    def run() -> None:
        """
        Fire 'startup' System Events, move the reactor to the 'running'
        state, then run the main loop until it is stopped with C{stop()} or
        C{crash()}.
        """

    def stop() -> None:
        """
        Fire 'shutdown' System Events, which will move the reactor to the
        'stopped' state and cause C{reactor.run()} to exit.
        """

    def crash() -> None:
        """
        Stop the main loop *immediately*, without firing any system events.

        This is named as it is because this is an extremely "rude" thing to do;
        it is possible to lose data and put your system in an inconsistent
        state by calling this.  However, it is necessary, as sometimes a system
        can become wedged in a pre-shutdown call.
        """

    def iterate(delay: float) -> None:
        """
        Run the main loop's I/O polling function for a period of time.

        This is most useful in applications where the UI is being drawn "as
        fast as possible", such as games. All pending L{IDelayedCall}s will
        be called.

        The reactor must have been started (via the C{run()} method) prior to
        any invocations of this method.  It must also be stopped manually
        after the last call to this method (via the C{stop()} method).  This
        method is not re-entrant: you must not call it recursively; in
        particular, you must not call it while the reactor is running.
        """

    def fireSystemEvent(eventType: str) -> None:
        """
        Fire a system-wide event.

        System-wide events are things like 'startup', 'shutdown', and
        'persist'.
        """

    def addSystemEventTrigger(
        phase: str,
        eventType: str,
        func: typing.Callable[..., typing.Any],
        *args: object,
        **kwargs: object
    ) -> typing.Any:
        """
        Add a function to be called when a system event occurs.

        Each "system event" in Twisted, such as 'startup', 'shutdown', and
        'persist', has 3 phases: 'before', 'during', and 'after' (in that
        order, of course).  These events will be fired internally by the
        Reactor.

        An implementor of this interface must only implement those events
        described here.

        Callbacks registered for the "before" phase may return either None or a
        Deferred.  The "during" phase will not execute until all of the
        Deferreds from the "before" phase have fired.

        Once the "during" phase is running, all of the remaining triggers must
        execute; their return values must be ignored.

        @param phase: a time to call the event -- either the string 'before',
                      'after', or 'during', describing when to call it
                      relative to the event's execution.
        @param eventType: this is a string describing the type of event.
        @param callable: the object to call before shutdown.
        @param args: the arguments to call it with.
        @param kwargs: the keyword arguments to call it with.

        @return: an ID that can be used to remove this call with
                 removeSystemEventTrigger.
        """

    def removeSystemEventTrigger(triggerID: typing.Any) -> None:
        """
        Removes a trigger added with addSystemEventTrigger.

        @param triggerID: a value returned from addSystemEventTrigger.

        @raise KeyError: If there is no system event trigger for the given
            C{triggerID}.
        @raise ValueError: If there is no system event trigger for the given
            C{triggerID}.
        @raise TypeError: If there is no system event trigger for the given
            C{triggerID}.
        """

    def callWhenRunning(func: typing.Callable[..., typing.Any], *args: object, **kwargs: object) -> typing.Optional[typing.Any]:
        """
        Call a function when the reactor is running.

        If the reactor has not started, the callable will be scheduled
        to run when it does start. Otherwise, the callable will be invoked
        immediately.

        @param callable: the callable object to call later.
        @param args: the arguments to call it with.
        @param kwargs: the keyword arguments to call it with.

        @return: None if the callable was invoked, otherwise a system
                 event id for the scheduled call.
        """


class IReactorTCP(ztk.Interface):
	def listenTCP(port: int, factory: "ServerFactory", backlog: int, interface: str) -> IListeningPort:
		"""Connects a given protocol factory to the given numeric TCP/IP port.

	@param port: a port number on which to listen
	@param factory: a L{twisted.internet.protocol.ServerFactory} instance
	@param backlog: size of the listen queue
	@param interface: The local IPv4 or IPv6 address to which to bind;
		defaults to '', ie all IPv4 addresses.  To bind to all IPv4 and IPv6
		addresses, you must call this method twice.

	@return: an object that provides L{IListeningPort}.

	@raise CannotListenError: as defined here
							  L{twisted.internet.error.CannotListenError},
							  if it cannot listen on this port (e.g., it
							  cannot bind to the required port number)"""

	def connectTCP(host: bytes, port: int, factory: "ClientFactory", timeout: float,
				   bindAddress: typing.Optional[typing.Tuple[str, int]]) -> IConnector:
		"""Connect a TCP client.

	@param host: A hostname or an IPv4 or IPv6 address literal.
	@param port: a port number
	@param factory: a L{twisted.internet.protocol.ClientFactory} instance
	@param timeout: number of seconds to wait before assuming the
					connection has failed.
	@param bindAddress: a (host, port) tuple of local address to bind
						to, or None.

	@return: An object which provides L{IConnector}. This connector will
			 call various callbacks on the factory when a connection is
			 made, failed, or lost - see
			 L{ClientFactory<twisted.internet.protocol.ClientFactory>}
			 docs for details."""


#class IReactorSSL
#class IReactorUNIX
#class IReactorUNIXDatagram
#class IReactorUDP
#class IReactorMulticast
#class IReactorSocket
#class IReactorProcess


class IReactorTime(ztk.Interface):
	"""Time methods that a Reactor should implement."""

	def seconds() -> float:
		"""Get the current time in seconds.

	@return: A number-like object of some sort."""

	def callLater(delay: float, callable, *args, **kwargs) -> 'IDelayedCall':
		"""Call a function later.

		@param delay: the number of seconds to wait.
		@param callable: the callable object to call later.
		@param args: the arguments to call it with.
		@param kwargs: the keyword arguments to call it with.

		@return: An object which provides L{IDelayedCall} and can be used to
				 cancel the scheduled call, by calling its C{cancel()} method.
				 It also may be rescheduled by calling its C{delay()} or
				 C{reset()} methods.
		"""

	def getDelayedCalls() -> typing.List['IDelayedCall']:
		"""
		Retrieve all currently scheduled delayed calls.

		@return: A list of L{IDelayedCall} providers representing all
				 currently scheduled calls. This is everything that has been
				 returned by C{callLater} but not yet called or cancelled.
		"""
